package com.bolt.report.engine.fill;

import com.bolt.common.collection.CollectionUtil;
import com.bolt.common.collection.IteratorUtil;
import com.bolt.common.reflect.ObjectUtil;
import com.bolt.report.definition.*;
import com.bolt.report.engine.*;
import com.bolt.report.engine.DynamicColumn;
import com.bolt.report.engine.model.*;
import com.bolt.report.exception.ReportException;
import com.bolt.report.util.ToolUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class GroupReportFiller extends BaseReportFiller {

    protected FillGroupAggregate fillGroupAggregate;

    protected List<GroupTree> downGroupTree;

    protected List<GroupTree> rightGroupTree;

    protected int dynamicColumnSize = 0;

    protected int startDynamicColumnIndex = 0;

    private int childRowIndex = 0;

    private DynamicHeader[] dynamicHeaders;


    public GroupReportFiller(ReportDefinition definition) {
        super(definition);
    }

    protected void fillReport() throws ReportException {
        fillReportStart();
        fillTitle();
        fillContext();
        fillSummary();
        fillReportEnd();
    }

    protected void fillReportStart() {
        isFirstTitleToPrint = true;
        fillGroupAggregate = new FillGroupAggregate(fillContext);
        computeGroup();
        fillColumn();
        page = new PageDetails(columns);
        fillContext.setPage(page);
        setFillHeight();
    }

    protected void fillContext() {
        fillTitle();
        fillColumnHeader();
        if (IteratorUtil.isNotEmpty(this.downGroupTree)) {
            int rowIndex = downGroupTree.get(0).getGroupField().getDefinition().getY();
            childRowIndex = rowIndex;
            fillDownGroup(this.downGroupTree, rowIndex);
        }
    }

    @Override
    protected void fillTitle() {
        isFirstTitleToPrint = false;
        if (definition.haveTitle()) {
            List<RowDefinition> rowDefinitions = definition.getTitle();
            Title title = new Title(columns, definition.isLoopPrintTitle());
            for (RowDefinition rowDefinition : rowDefinitions) {
                List<Cell> cells = new ArrayList<>();
                for (CellDefinition element : rowDefinition.getCellDefinitions()) {
                    if (element.getY() == 0 && element.getColSpan() > 0 && element.getColSpan() < dynamicColumnSize) {
                        element.setColSpan(this.columns.size());
                    }
                    Cell cell = ObjectFactory.createCell(element, fillContext, null, element.getY(), element.getX());
                    cells.add(cell);
                }
                title.appendRowCell(cells, rowDefinition.getHeight());
            }
            reportPrint.setTitle(title);

        } else {
            List<RowDefinition> rowDefinitions = definition.getElements();
            for (RowDefinition rowDefinition : rowDefinitions) {
                List<CellDefinition> staticCells = definition.getStaticCell(rowDefinition.getCellDefinitions(),fillContext);
                fillStaticCell(null,staticCells, rowDefinition.getHeight(),true);
            }
        }
    }

    private List<Cell> createCells(List<CellDefinition> cellDefinitions) {
        int rowIndex = page.getCurrentRowSize();
        rowIndex = 0 == rowIndex ? rowIndex : rowIndex - 1;
        List<Cell> cells = new ArrayList<>(cellDefinitions.size());
        for (CellDefinition element : cellDefinitions) {
            if (rowIndex < element.getY()) {
                rowIndex = element.getY();
            }
            Cell cell = ObjectFactory.createCell(element, fillContext, null, rowIndex, element.getX());
            StyleDefinition style = element.getStyle();
            cell.setStyle(style);
            cell.setRowSpan(element.getRowSpan());
            cell.setColSpan(element.getColSpan());
            cells.add(cell);
        }
        return cells;
    }

    @Override
    protected void fillColumnHeader() {
        isFirstHeaderToPrint = false;
        if (definition.haveHead() && IteratorUtil.isNotEmpty(this.rightGroupTree)) {
            ColumnHeader columnHeader = new ColumnHeader(columns, definition.isLoopPrintHead());
            for (RowDefinition rowDefinition : definition.getHeads()) {
                List<CellDefinition> staticCells = definition.getStaticCell(rowDefinition.getCellDefinitions(),fillContext);
                columnHeader.insertCells(createCells(staticCells), rowDefinition.getHeight());
            }
            fillRightGroup(this.rightGroupTree, columnHeader);
            reportPrint.setColumnHeader(columnHeader);
        } else if (definition.haveHead() && IteratorUtil.isEmpty(this.rightGroupTree)) {
            super.fillColumnHeader();
        } else {
            if (IteratorUtil.isNotEmpty(this.rightGroupTree)) {
                fillRightGroup(this.rightGroupTree, null);
            }
        }
    }

    @Override
    protected void fillStaticCell(List<Cell> cells ,List<CellDefinition> staticCells, int rowHeight,boolean insert) {
        for (CellDefinition cellDefinition : staticCells) {
            if (cellDefinition.getColSpan() < dynamicColumnSize && cellDefinition.getY() == 0) {
                cellDefinition.setColSpan(this.columns.size());
            }
        }
        super.fillStaticCell(null,staticCells, rowHeight,true);
    }


    private void computeGroup() {
        List<BRGroupField> groupFields = reportPrint.getGroupFields();
        if (IteratorUtil.isEmpty(groupFields)) {
            throw new ReportException("group is empty");
        }
        Set<String> keys = groupFields.stream().map(k -> k.getKey()).distinct().collect(Collectors.toSet());
        if (keys.size() > 1) {
            throw new ReportException("group dataset must be single ");
        }
        String dataSetKey = keys.iterator().next();
        FillReturnValue returnValues = fillContext.getReturnValue(dataSetKey);

        List<BRGroupField> downGroups = groupFields.stream().filter(k -> k.getDataExpand() == DataExpand.DOWN)
                .collect(Collectors.toList());

        List<BRGroupField> rightGroups = groupFields.stream().filter(k -> k.getDataExpand() == DataExpand.RIGHT)
                .collect(Collectors.toList());
        if (IteratorUtil.isNotEmpty(downGroups)) {
            downGroupTree = fillGroupAggregate.doGroup(returnValues.getRawData(), downGroups, 0);
        }
        if (IteratorUtil.isNotEmpty(rightGroups)) {
            rightGroupTree = fillGroupAggregate.doGroup(returnValues.getRawData(), rightGroups, 0);
        }
    }

    @Override
    protected void fillColumn() {
        super.fillColumn();
        if (IteratorUtil.isNotEmpty(rightGroupTree)) {
            CellDefinition definition = rightGroupTree.get(0).getGroupField().getDefinition();
            dynamicColumnSize = fillGroupAggregate.getLeaf(rightGroupTree).size();
            startDynamicColumnIndex = definition.getX();
            List<Integer> columnDef = this.definition.getColumnDefinition();
            this.columns.remove(definition.getX());
            for (int index = 0; index < dynamicColumnSize; index++) {
                Column column = new Column(index);
                int location = definition.getX() + index;
                column.setWidth(columnDef.get(definition.getX()));
                column.setIndex(location);
                this.columns.add(location, column);
            }
            for (int i = 0; i < this.columns.size(); i++) {
                Column column = columns.get(i);
                column.setIndex(i);
            }
            for (RowDefinition rowDefinition : this.definition.getElements()) {
                for (CellDefinition cellDefinition : this.definition.getStaticCell(rowDefinition.getCellDefinitions(),fillContext)) {
                    int columnIndex = cellDefinition.getX();
                    if (columnIndex > startDynamicColumnIndex && IteratorUtil.isNotEmpty(this.rightGroupTree)) {
                        cellDefinition.setX(cellDefinition.getX() + dynamicColumnSize - 1);
                    }
                }
            }

        }
        dynamicHeaders = new DynamicHeader[dynamicColumnSize];
        for (int i = 0; i < dynamicColumnSize; i++) {
            dynamicHeaders[i] = new DynamicHeader();
        }
    }


    private Cell newRightExpandCell(GroupTree groupTree, int x) {
        int colSize = groupTree.getSize();
        GroupItem groupItem = groupTree.getGroupItem();
        CellDefinition definition = groupTree.getGroupField().getDefinition();

        BindData bindData = new BindData(definition.getKey(), definition.getName(), groupItem.getValue(),null);
        Cell cell = ObjectFactory.createCell(definition, fillContext, bindData,  definition.getY(), definition.getX() + x);
        cell.setColSpan(colSize);
        cell.accept(fillContext.getElementVisitor());
        this.dynamicHeaders[x].add(new DynamicColumn(definition, groupItem.getValue()));
        return cell;
    }

    private Cell newDownExpandCell(GroupTree groupTree, int rowSpan, int y) {
        GroupItem groupItem = groupTree.getGroupItem();
        CellDefinition definition = groupTree.getGroupField().getDefinition();
        BindData bindData = new BindData(definition.getKey(), definition.getName(), groupItem.getValue(),null);
        Cell cell = ObjectFactory.createCell(definition, fillContext, bindData,  y, definition.getX());
        cell.setRowSpan(rowSpan);
        cell.accept(fillContext.getElementVisitor());
        return cell;
    }

    private void fillRightGroup(List<GroupTree> rightGroupTree, BaseBand band) {
        List<Cell> cells = new ArrayList<>();
        CellDefinition definition = null;
        for (int k = 0; k < rightGroupTree.size(); k++) {
            GroupTree parentGroup = rightGroupTree.get(k);
            definition = parentGroup.getGroupField().getDefinition();
            cells.add(newRightExpandCell(parentGroup, k));
            List<GroupTree> children = parentGroup.getChildren();
            if (IteratorUtil.isNotEmpty(children)) {
                fillRightGroup(children, band);
            }
        }
        if (null != band) {
            band.insertCells(cells, definition.getHeight());
        } else {
            fillRow(cells, definition.getHeight());
        }
    }

    private void fillDownGroup(List<GroupTree> downGroupTree, int rowIndex) {
        List<Cell> cells = new ArrayList<>();
        CellDefinition cellDefinition = null;

        for (int k = 0; k < downGroupTree.size(); k++) {
            GroupTree groupTree = downGroupTree.get(k);
            int rowSpan = fillGroupAggregate.computeRowSpan(groupTree);
            cellDefinition = groupTree.getGroupField().getDefinition();
            cells.add(newDownExpandCell(groupTree, rowSpan, rowIndex));

            List<GroupTree> children = groupTree.getChildren();
            if (IteratorUtil.isNotEmpty(children)) {
                fillDownGroup(children, rowIndex);
                fillGroupSummary(groupTree);
                rowIndex = childRowIndex;
            } else {
                fillDataCell(groupTree);
                fillGroupSummary(groupTree);
                rowIndex = childRowIndex;
            }
        }
        RowDefinition rowDefinition = definition.getElements().get(cellDefinition.getY());
        fillRow(cells, rowDefinition.getHeight());
    }

    public Object getValue(Object data, String expression, int columnIndex) {
        if (dynamicHeaders.length != 0) {
            boolean isMatch = false;
            DynamicHeader dynamicHeader = dynamicHeaders[columnIndex];
            for (DynamicColumn dynamicColumn : dynamicHeader.getColumns()) {
                CellDefinition definition = dynamicColumn.getDefinition();
                Object match = ObjectFactory.getValueByExpression(definition.getName(), data);
                isMatch = ObjectUtil.equal(match, dynamicColumn.getData());
            }
            if (isMatch) {
                return ObjectFactory.getValueByExpression(expression, data);
            } else {
                return null;
            }
        } else {
            return ObjectFactory.getValueByExpression(expression, data);
        }
    }

    public void fillDataCell(GroupTree groupTree) {
        DataRange dataRange = groupTree.getGroupItem().getRange();
        List<?> bindData = dataRange.getBindData();
        CellDefinition cellDefinition = groupTree.getGroupField().getDefinition();
        RowDefinition rowDefinition = definition.getElements().get(cellDefinition.getY());
        List<CellDefinition> allDefinition = definition.getRowCell(rowDefinition.getCellDefinitions());
        int expandSize = this.dynamicColumnSize > 0 ? this.dynamicColumnSize : 1;
        for (int i = 0; i < bindData.size(); i++) {
            Object data = bindData.get(i);
            List<Cell> cells = new ArrayList<>();
            for (int k = 0; k < allDefinition.size(); k++) {
                CellDefinition element = allDefinition.get(k);
                if (element.getElementType() == ElementType.TEXT) {
                    Cell cell = ObjectFactory.createCell(element, fillContext, null, childRowIndex, element.getX());
                    cells.add(cell);
                } else {
                    for (int j = 0; j < expandSize; j++) {
                        int columnIndex = element.getX() + j;
                        BindData value = new BindData(element.getKey(),element.getName(),getValue(data, element.getName(), j),null);
                        Cell cell = ObjectFactory.createCell(element, fillContext, value, childRowIndex, columnIndex);
                        cells.add(cell);
                    }
                }
            }
            fillRow(cells, rowDefinition.getHeight());
            childRowIndex++;
        }
    }

    private List<?> getCollectionValue(List<?> data, String expression, int columnIndex) {
        if (dynamicHeaders.length != 0) {
            return data.stream().filter(k -> {
                boolean isMatch = false;
                DynamicHeader dynamicHeader = dynamicHeaders[columnIndex];
                for (DynamicColumn dynamicColumn : dynamicHeader.getColumns()) {
                    CellDefinition definition = dynamicColumn.getDefinition();
                    Object match = ObjectFactory.getValueByExpression(definition.getName(), k);
                    isMatch = ObjectUtil.equal(match, dynamicColumn.getData());
                }
                return isMatch;
            }).map(k -> ObjectFactory.getValueByExpression(expression, k)).collect(Collectors.toList());
        } else {
            return data.stream().map(k -> ObjectFactory.getValueByExpression(expression, k)).collect(Collectors.toList());
        }
    }


    public void fillGroupSummary(GroupTree groupTree) {
        if (definition.haveGroupSummary()) {
            GroupTree firstGroupTree = this.downGroupTree.get(0);
            DataRange dataRange = groupTree.getGroupItem().getRange();
            List<?> bindData = dataRange.getBindData();
            CellDefinition currentGroupCell = groupTree.getGroupField().getDefinition();
            CellDefinition firstGroupCell = firstGroupTree.getGroupField().getDefinition();
            List<Cell> groupSummaryCells = new ArrayList<>();
            List<RowDefinition> groupSummary = definition.getGroupSummary();
            int expandSize = this.dynamicColumnSize > 0 ? this.dynamicColumnSize : 1;
            for (RowDefinition groupSummaryRow : groupSummary) {
                for (CellDefinition summaryCell : groupSummaryRow.getCellDefinitions()) {
                    if (summaryCell.getElementType() == ElementType.TEXT) {
                        Cell cell = ObjectFactory.createCell(summaryCell, fillContext, null, childRowIndex, summaryCell.getX());
                        if (summaryCell.getX() >= firstGroupCell.getX() && summaryCell.getX() <= currentGroupCell.getX()) {
                            List<CellDefinition> dataSets = definition.getDataSetCell(groupSummaryRow.getCellDefinitions());
                            if (IteratorUtil.isNotEmpty(dataSets)) {
                                int firstIndex = dataSets.get(0).getX();
                                cell.setColSpan(firstIndex - currentGroupCell.getX());
                                cell.setColumnIndex(currentGroupCell.getX());
                            }
                        }
                        if (summaryCell.getX() > startDynamicColumnIndex) {
                            cell.setColumnIndex(summaryCell.getX() + dynamicColumnSize - 1);
                        }
                        groupSummaryCells.add(cell);
                    } else {
                        for (int j = 0; j < expandSize; j++) {
                            int columnIndex = summaryCell.getX() + j;
                            BindData value = new BindData(summaryCell.getKey(),summaryCell.getName(),getCollectionValue(bindData, summaryCell.getName(), j),null);
                            Cell cell = ObjectFactory.createCell(summaryCell, fillContext, value, childRowIndex, columnIndex);
                            groupSummaryCells.add(cell);
                        }
                    }
                }
                fillRow(groupSummaryCells, groupSummaryRow.getHeight());
                childRowIndex++;
            }
        }
    }

    @Override
    protected void fillRow(List<Cell> cells, int rowHeight) {
        page.insertCells(cells, rowHeight);
    }

    protected void addPage(int startRow, int pageSize) {
        setFillHeight();
        List<Row> subRow = CollectionUtil.sub(page.getRow(), startRow, pageSize);
        PageDetails newPage = new PageDetails(page.getColumn(), subRow);
        reportPrint.addPage(newPage);

    }

    protected void fillReportEnd() {
        if (!ignorePagination) {
            int startRow = 0;
            int rowSize = page.getRowSize();
            List<BRGroupField> groupFields = reportPrint.getGroup();
            for (int i = 0; i < rowSize; i++) {
                int pageSize = i;
                Row row = page.getRow().get(i);
                fillHeight = fillHeight + ToolUtil.pixelToPoint(row.getHeight());
                if (fillHeight > contextHeight) {
                    addPage(startRow, pageSize);
                    startRow = pageSize;
                }
            }
            if (page.getRowSize() > startRow) {
                List<Row> subRow = CollectionUtil.sub(page.getRow(), startRow, page.getRowSize());
                PageDetails newPage = new PageDetails(page.getColumn(), subRow);
                reportPrint.addPage(newPage);
            }
            List<PageDetails> pages = reportPrint.getPages();
            for (int i = 1; i < pages.size(); i++) {
                PageDetails page = pages.get(i);
                PageDetails prevPage = pages.get(i - 1);
                for (BRGroupField groupField : groupFields) {
                    if (!groupField.getDataExpand().equals(DataExpand.DOWN)) {
                        continue;
                    }
                    int columnIndex = definition.getElementIndex(groupField.getName());
                    int prevRowSize = prevPage.getCurrentRowSize();
                    Cell prevLastGroupCell = prevPage.findLastGroupCell(columnIndex);


                    if (prevLastGroupCell == null) continue;
                    int prevRowSpan = prevLastGroupCell.getRowSpan();
                    if (prevRowSpan + prevLastGroupCell.getRowIndex() > prevRowSize) {
                        int newRowRowSpan = prevRowSize - prevLastGroupCell.getRowIndex();
                        Cell currentCell = page.getCell(0, prevLastGroupCell.getColumnIndex());
                        int cellIndex = prevLastGroupCell.getColumnIndex();
                        if (currentCell != null) {
                            currentCell.setColumnIndex(columnIndex + 1);
                            page.insertCell(currentCell);
                        }
                        Cell nextCell = new Cell(0, cellIndex, prevLastGroupCell.getElement());
                        nextCell.setStyle(prevLastGroupCell.getStyle());
                        nextCell.setRowSpan(prevRowSpan - newRowRowSpan);
                        nextCell.accept(fillContext.getElementVisitor());
                        prevLastGroupCell.setRowSpan(newRowRowSpan);
                        page.insertCell(nextCell);
                    }
                }
            }
        } else {
            reportPrint.addPage(page);
        }
    }
}
